package com.sduept.nwld.dataserver.manager.faultforecast;

import com.sduept.bigdata.fault.entity.FaultRecord;
import com.sduept.bigdata.fault.manager.FaultRecordQueryManager;
import com.sduept.bigdata.weather.entity.IceData;
import com.sduept.bigdata.weather.entity.IceMonitor;
import com.sduept.bigdata.weather.manager.IceDataManager;
import com.sduept.bigdata.weather.manager.IceMonitorManager;
import com.sduept.core.dao.SuperDAO;
import com.sduept.nwld.dataserver.model.faultforecast.IceFaultSample;
import com.sduept.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.*;

/**
 * 抽取持续覆冰数据样本。
 * 
 * <br/>持续覆冰的依据是：覆冰监测设备上传的数据。目前只有线路上有覆冰检测设备。
 * 一条线上可能有 1-n个杆塔上安装覆冰监测设备。
 * 每个杆塔可能同时装1-m个同一编号的监测设备，用来监测本杆塔上各个相别的线路覆冰情况。
 * 每个覆冰监测设备都会定时上传覆冰厚度数据。但是不同杆塔的覆冰数据上传时间并不同步，时间间隔也不同。
 * 
 * <br/>持续覆冰样本：计算出每个杆塔的持续覆冰情况。一个杆塔只要有一个覆冰设备上传数据不为0，记为在持续覆冰。
 * 若两次持续上传数据时间间隔超过2小时，或上传0数据超过两个小时，即为覆冰中断。
 * 
 * <br/>故障信息：在一次持续覆冰时间内，可能发生0-多次覆冰故障。
 * 根据持续覆冰的时间区间及覆冰线路的id，可以拿到持续覆冰期间的故障次数及经纬度。
 * 
 * <br/>没有考虑无覆冰数据时发生覆冰故障的情况。
 */

@Service
@Slf4j
public class IceFaultForecastSampleGenManager {
	@Autowired
	private SuperDAO dao;
	@Autowired
	private FaultRecordQueryManager faultManager;
	@Autowired
	private IceDataManager iceDataManager;
	@Autowired
	private IceMonitorManager monitorManager;
	@Autowired
	private IceFaultForecastSampleManager sampleManager;

	public void genSample(Date startTime,Date endTime) {
		GenManager gm = new GenManager();
		gm.run(startTime, endTime);
	}
	
	public void genFaultInfo(List<IceFaultSample> samples,List<FaultRecord> faults) {
		GenManager gm = new GenManager();
		samples = gm.addIceFaultInfo(samples, faults);
		try {
			sampleManager.save(samples);
		} catch (SQLException e) {
			log.warn("覆冰故障信息填写失败");
			e.printStackTrace();
		}
	}
	
	public class GenManager{
		private Map<String,IceMonitor> monitorMapBaseBs;

		//2小时以上未上传数据或上传的数据为0，就判定覆冰中断
		private int ICING_MAX_HOUR_WITHOUT_DATA = 2;

		//6小时，共一千三百左右设备，假设每个设备一小时上传6次，6小时就是8000多条数据。
		private int QUERY_MAX_HOUR = 8;
		//一次保存10天的内容
		private int persistMaxDay = 10;
		
		private Set<String> notExitMonitors = new HashSet<>();
		
		GenManager(){
			init();
		}
		private void init() {
			monitorMapBaseBs = getMonitorMapBaseBs();
		}
		public void run(Date startTime,Date endTime) {
			List<IceFaultSample> icingSamples = new ArrayList<>();
			Date currentTime = new Date(startTime.getTime());
			//一次存10天的数据
			while(currentTime.before(endTime)) {
				Date eTime = DateUtil.addDays(currentTime, persistMaxDay);
				if(eTime.after(endTime)) {
					eTime = endTime;
				}
				icingSamples = caculate(currentTime,eTime);
				try {
					sampleManager.save(icingSamples);
				} catch (SQLException e) {
					log.warn(currentTime.toString()+"~"+eTime.toString()+"覆冰样本失败");
					e.printStackTrace();
				}
				/*log.info(DateUtil.getStringDate(currentTime,DateUtil.pattern_ymdhms)
						+"~"+DateUtil.getStringDate(eTime,DateUtil.pattern_ymdhms)+"覆冰样本生成成功");*/
				currentTime = eTime;
			}
			
			/*//将不存在的覆冰设备存表
			List<IceMonitorNotExit> iceMonitorsNotExit = new ArrayList<>();
			for(String bsId :notExitMonitors) {
				IceMonitorNotExit monitor = new IceMonitorNotExit(bsId);
				iceMonitorsNotExit.add(monitor);
			}
			try {
				dao.persistentBatchUpdate(IceMonitorNotExit.class, iceMonitorsNotExit);
			} catch (SQLException e) {

				e.printStackTrace();
			}*/
		}
		public List<IceFaultSample> caculate(Date startTime,Date endTime) {

			List<IceFaultSample> icingSamples = new ArrayList<>();

			//拿到数据库中时间上一阶段的可能未结束覆冰数据。
			Date beforeStart = DateUtil.addSeconds(startTime , -1 * ICING_MAX_HOUR_WITHOUT_DATA * 3600 ); 
			List<IceFaultSample> lastSamples = sampleManager.findByIceEndTime(beforeStart, endTime);
			if(lastSamples != null && lastSamples.size()>0) {
				icingSamples.addAll(lastSamples);
			}

			//由于覆冰数据比较多，一次取最多取8小时数据进行计算。
			Date currentTime = new Date(startTime.getTime());
			while(currentTime.before(endTime)) {
				Date eTime = DateUtil.addSeconds(currentTime, QUERY_MAX_HOUR*3600);
				if(eTime.after(endTime)) {
					eTime = endTime;
				}
				List<IceData> iceDatas = iceDataManager.findByUpdateTime(currentTime, eTime);
				List<IceFaultSample> samples = caculateIcingSampleBaseTower(iceDatas);
				if(samples != null && samples.size()>0) {
					icingSamples.addAll(samples);
				}
				currentTime = eTime;
			}
			//检查因为分次取数据，导致的覆冰中断的情况
			//如果前后两次覆冰时差小于2小时的，进行合并
			icingSamples = mergeIcingSample(icingSamples);
			//去掉时长小于0.1的数据
			Iterator<IceFaultSample> ite3 = icingSamples.iterator();
			while(ite3.hasNext()) {
				if(ite3.next().getDuration() <= 0.01d) {
					ite3.remove();
				}
			}
			
			//添加故障信息
			List<FaultRecord> faults = faultManager.findFaultByTimeReason(startTime, endTime, "覆冰");
			icingSamples = addIceFaultInfo(icingSamples,faults);

			return icingSamples;
		}
		public List<IceFaultSample> start(Date startTime,Date endTime,Integer index) {
			QUERY_MAX_HOUR = index;
			return caculate(startTime, endTime);
		}
		
		/**
		 * 如果前后两次覆冰时差小于2小时的，进行合并
		 * @param icingSamples
		 * @return
		 */
		private List<IceFaultSample> mergeIcingSample(List<IceFaultSample> icingSamples){
			//按照 设备+时间 排序
			Collections.sort(icingSamples, new Comparator<IceFaultSample>() {
				@Override
				public int compare(IceFaultSample o1, IceFaultSample o2) {
					if(o1.getMonitorId().compareTo(o2.getMonitorId()) == 0) {
						return o1.getStartTime().compareTo( o2.getStartTime()) ;
					}else {
						return o1.getMonitorId().compareTo( o2.getMonitorId());
					}
				}
			});
			int dynamicLength = icingSamples.size();
			for(int i=0;i<dynamicLength;i++) {
				IceFaultSample sample = icingSamples.get(i);
				String bsId = sample.getMonitorId();
				int k=i+1;
				while(k<dynamicLength) {
					//找到本设备的下一次覆冰，如果中断时间在2小时内，就进行合并。
					IceFaultSample nextSample = null;
					for(;k<icingSamples.size();k++) {
						IceFaultSample s = icingSamples.get(k);
						if(s.getMonitorId().equals(bsId)) {
							if(s.getStartTime().getTime() - sample.getEndTime().getTime() <= 3600000*ICING_MAX_HOUR_WITHOUT_DATA) {
								nextSample = s;
								icingSamples.remove(k);
								dynamicLength --;
							}else {
								k ++;
							}
							break;
						}
					}
					//合并两次覆冰数据
					if(nextSample != null) {
						Date gapStartTime = sample.getStartTime();
						Date gapEndTime = nextSample.getEndTime();
						List<IceData> currentIceData = iceDataManager.findByUpdateTime(gapStartTime, gapEndTime,bsId);
						//遍历数据，拿到两次持续覆冰中间的数据
						String lastId = sample.getId();
						IceFaultSample tempSample = genIcingSample(bsId,currentIceData);
						if(tempSample != null) {
							sample = tempSample;
							sample.setId(lastId);
							icingSamples.set(i, sample);
						}
					}
				}
			}
			return icingSamples;
		}
		
		private List<IceFaultSample> caculateIcingSampleBaseTower(List<IceData> iceDatas) {
			//将覆冰数据按照 bs_id+时间排序
			Collections.sort(iceDatas, new Comparator<IceData>() {
				
				@Override
				public int compare(IceData o1, IceData o2) {
					if(o1.getRecBsId().compareTo(o2.getRecBsId()) == 0) {
						return o1.getRecUpdateTime().compareTo( o2.getRecUpdateTime());
					}else {
						return o1.getRecBsId().compareTo(o2.getRecBsId());
					}
				}
				
			});
			//key 为 bsId,相别。
			List<IceFaultSample> iceSamples = new ArrayList<>();
			String lastBsId = "";
			Integer startIndex = null;
			Integer endIndex = null;
			Date lastNotZeroTime = null;
			for(int i=0;i<iceDatas.size();i++) {
				IceData iceData = iceDatas.get(i);
				String bsId = iceData.getRecBsId();
				//同一个覆冰设备时
				if(lastBsId.equals(bsId)) {
					//超过覆冰中断时限
					if(lastNotZeroTime != null 
							&& iceData.getRecUpdateTime().getTime() > lastNotZeroTime.getTime() + 3600000*ICING_MAX_HOUR_WITHOUT_DATA) {
						IceFaultSample sample = genIcingSample(bsId, iceDatas.subList(startIndex, endIndex+1));
						if(sample != null) {
							iceSamples.add(sample);
							startIndex = null;
							endIndex = null;
							lastNotZeroTime = null;
						}
					}
					//覆冰
					if(iceData.getRecIceHeight() > 0) {
						//开始一段覆冰
						if(startIndex == null) {
							startIndex = i;
						}
						endIndex = i;
						lastNotZeroTime = iceData.getRecUpdateTime();
					}
					
				}else {
					//设备改变时，保存上一段覆冰数据
					if(endIndex != null) {
						IceFaultSample sample = genIcingSample(lastBsId, iceDatas.subList(startIndex, endIndex+1));
						if(sample != null) {
							iceSamples.add(sample);
							startIndex = null;
							endIndex = null;
							lastNotZeroTime = null;
						}
					}
					//设备改变时，若覆冰厚度大于0，开始一个新的覆冰
					if(iceData.getRecIceHeight()>0) {
						startIndex = i;
						endIndex = i;
						lastNotZeroTime = iceData.getRecUpdateTime();
						lastBsId = bsId;
					}
				}
			}
			if(endIndex != null) {
				IceFaultSample sample = genIcingSample(lastBsId, iceDatas.subList(startIndex, endIndex+1));
				if(sample != null) {
					iceSamples.add(sample);
				}
			}
			
			return iceSamples;
		}
		
		private IceFaultSample genIcingSample(String bsId,List<IceData> iceData) {
			if(iceData == null || iceData.size() == 0) {
				return null;
			}
			int recordCount = iceData.size();
			IceMonitor monitor = monitorMapBaseBs.get(bsId);
			if(monitor == null) {
				notExitMonitors.add(bsId);
				return null;
			}
			IceFaultSample sample = new IceFaultSample(monitor);
			sample.setStartTime(iceData.get(0).getRecUpdateTime());
			sample.setEndTime(iceData.get(recordCount -1).getRecUpdateTime());
			sample.setDuration((sample.getEndTime().getTime() - sample.getStartTime().getTime())/3600000d);
			double sum = 0d;
			double maxHeight = 0d;
			for(int i=0;i<recordCount;i++) {
				IceData d = iceData.get(i);
				Double b = new BigDecimal(String.valueOf(d.getRecIceHeight())).doubleValue();  
				if(b > maxHeight) {
					maxHeight = b;
				}
				sum += b;
			}
			sample.setIceRecordCount(recordCount);
			sample.setAveHeight(sum/recordCount);
			sample.setMaxHeight(maxHeight);
			return sample;
		}
		
		/**
		 * 为杆塔持续覆冰样本添加覆冰故障数据。
		 * （没有考虑 无覆冰数据时的覆冰故障）
		 * @param samples
		 * @param faults
		 * @return
		 */
		private List<IceFaultSample> addIceFaultInfo(List<IceFaultSample> samples,List<FaultRecord> faults){
			Map<String,List<FaultRecord>> faultsMap = new HashMap<>();
			for(FaultRecord fault:faults) {
				if(fault.getStartPole() == null) {
					fault.setStartPole(1);
				}
				if("线路".equals(fault.getEtype()) 
						&& StringUtils.isNotBlank(fault.getEquipment_id())) {
					List<FaultRecord> efaults = faultsMap.get(fault.getEquipment_id());
					if(efaults == null) {
						efaults = new ArrayList<>();
						faultsMap.put(fault.getEquipment_id(), efaults);
					}
					efaults.add(fault);
				}
			}
			for(IceFaultSample sample : samples) {
				String eid = sample.getLineId();
				if(StringUtils.isBlank(eid)) {
					continue;
				}
				List<FaultRecord> fs = faultsMap.get(eid);
				if(fs == null || fs.size() == 0) {
					continue;
				}
				IceMonitor monitor = monitorMapBaseBs.get(sample.getMonitorId());
				if(monitor == null) {
					continue;
				}
				List<FaultRecord> icingFaults = new ArrayList<>();
				for(FaultRecord fault:fs) {
					String linePoles = monitor.getLinePoles();
					if(sample.getStartTime().before(fault.getFaultTime())
							&& sample.getEndTime().after(fault.getFaultTime()) 
							&& StringUtils.isNotBlank(linePoles)) {
						//看杆塔
						if(checkPoleInPoles(fault.getStartPole(),linePoles)) {
							icingFaults.add(fault);
						}
					}
				}
				int faultSize = icingFaults.size();
				if(faultSize > 0 ) {
					sample.setFaultNum(faultSize);
					String lonStr = "";
					String latStr = "";
					String faultIds = "";
					
					for(int i=0;i<faultSize;i++) {
						FaultRecord fault = faults.get(i);
						faultIds += fault.getId()+",";
						if(fault.getLon() == null) {
							lonStr = ",";
						}else {
							lonStr += fault.getLon() + ",";
						}
						if(fault.getLat() == null) {
							latStr = ",";
						}else {
							latStr += fault.getLat() + ",";
						}
					}
					sample.setFaultIds(faultIds.substring(0, faultIds.length()-1));
					if(StringUtils.isNotBlank(lonStr)) {
						lonStr = lonStr.substring(0, lonStr.length()-1);
						sample.setFaultLons(lonStr);
					}
					if(StringUtils.isNotBlank(latStr)) {
						latStr = lonStr.substring(0, latStr.length()-1);
						sample.setFaultLats(latStr);
					}
					
				}
			}
			return samples;
			
		}

		private boolean checkPoleInPoles(Integer pole,String poles) {
			if(StringUtils.isEmpty(poles) || pole == null) {
				return false;
			}
			String[] ps = poles.split("-");
			if(ps.length == 2 && NumberUtils.isParsable(ps[0]) && NumberUtils.isParsable(ps[1])) {
				if(Double.parseDouble(ps[0]) <= pole && Double.parseDouble(ps[1]) >= pole) {
					return true;
				}
			}
			return false;
		}
		
		/**
		 * 计算杆塔上各个相别的持续覆冰样本数据
		 * @param startTime
		 * @param endTime
		 * @return
		 */
		private List<IceFaultSample> caculateIcingSampleBasePhase(Date startTime,Date endTime){
			List<IceFaultSample> icingSamples = new ArrayList<>();
			/*//key 为 bsId,相别
			Map<String,IceFaultSample> samplesMap = new HashMap<>();

			//拿到数据库内覆冰结束时间 与 当前计算的覆冰开始时间，不超过2小时的数据。
			Date beforeStart = DateUtil.addSeconds(startTime , -1 * ICING_MAX_HOUR_WITHOUT_DATA * 3600 ); 
			List<IceFaultSample> lastSamples = sampleManager.findByIceEndTime(beforeStart, endTime);
			icingSamples.addAll(lastSamples);

			//由于覆冰数据比较多，一次取最多取一万条数据进行计算。
			long count = iceDataManager.count(startTime, endTime);
			for(int i=0;i < count;i+=QUERY_ICE_MAX_LENGTH) {
				List<IceData> iceDatas = iceDataManager.findByUpdateTime(startTime, endTime, i, i+QUERY_ICE_MAX_LENGTH);
				//求出每一个覆冰设备的覆冰持续情况。
				for(IceData iceData:iceDatas) {
					String key = iceData.getRecBsId() + "," + iceData.getRecPhase();
					IceFaultSample sample = samplesMap.get(key);
					Double iceHeight = iceData.getRecIceHeight().doubleValue();
					if(sample == null) {
						if(iceHeight > 0) {
							IceMonitor monitor = monitorMap.get(key);
							if(monitor == null) {
								log.warn("覆冰设备"+key+"在覆冰设备表中无记录，但是有上传覆冰数据。");
								continue;
							}
							sample = new IceFaultSample(monitor, iceData);
							samplesMap.put(key, sample);
						}
					}else {
						if(iceHeight == 0) {
							//本次覆冰中断
							icingSamples.add(sample);
							samplesMap.remove(key);
						}else {
							Date lastTime = sample.getEndTime();
							Date thisTime = iceData.getRecUpdateTime();
							//持续覆冰
							if(thisTime.getTime() - lastTime.getTime() <= 3600000*ICING_MAX_HOUR_WITHOUT_DATA) {
								sample.setEndTime(thisTime);
								sample.setDuration((thisTime.getTime() - sample.getStartTime().getTime())/3600000d);
								Double ave = (sample.getAveHeight() * sample.getIceRecordCount() + iceHeight)/(sample.getIceRecordCount()+1);
								sample.setAveHeight(ave);
								sample.setIceRecordCount(sample.getIceRecordCount()+1);
								if(iceHeight > sample.getMaxHeight()) {
									sample.setMaxHeight(iceHeight);
								}
							}else {
								//中断覆冰
								icingSamples.add(sample);
								samplesMap.remove(key);
								sample = new IceFaultSample(monitorMap.get(key), iceData);
								samplesMap.put(key, sample);
							}
						}
					}
				}
			}
			//将未结束的覆冰数据填入
			icingSamples.addAll(samplesMap.values());*/
			return icingSamples;
		}

		/**
		 * 将单相别持续覆冰样本数据，合并为杆塔持续覆冰样本数据
		 * 样本需要按照 bs_id+时间排序
		 * 
		 * 废弃原因：
		 * 因为加了2小时的允许拖延时间，而在这两小时内，可能会存在上传数据为0的点，但是没有被存下来。
		 * @param phaseIcingSamples
		 * @return
		 */
		@Deprecated
		private List<IceFaultSample> combineFromPhaseIcingToTowerIcingSamples(List<IceFaultSample> phaseIcingSamples) {
			//将同一个杆塔的不同相别的检测设备数据合并。只要ABC相有一个在覆冰，即定为覆冰。平均值为所有相别的平均值。
			//已经按照 monitorId 和 时间 排序
			List<IceFaultSample> combineSamples = new ArrayList<>();
			String lastMonitorId = "";
			IceFaultSample combineSample = null;
			//将覆冰数据按照时间分段
			for(IceFaultSample sample : phaseIcingSamples) {
				String currentMonitorId = sample.getMonitorId();
				Date currentStartTime = sample.getStartTime();
				Date currentEndTime = sample.getEndTime();
				if(lastMonitorId.equals(currentMonitorId)) {
					//覆冰持续时间有重合部分，取并集。
					if( currentStartTime.getTime() >= combineSample.getStartTime().getTime() 
							&& currentStartTime.getTime() <= combineSample.getEndTime().getTime() + ICING_MAX_HOUR_WITHOUT_DATA*3600000) {
						Double iceHeightSum = combineSample.getAveHeight() * combineSample.getIceRecordCount() + sample.getAveHeight() * sample.getIceRecordCount();
						Integer iceRecordCount = combineSample.getIceRecordCount() + sample.getIceRecordCount();
						combineSample.setAveHeight(iceHeightSum/iceRecordCount);
						combineSample.setIceRecordCount(iceRecordCount);
						combineSample.setMaxHeight(Math.max(combineSample.getMaxHeight(), sample.getMaxHeight()));

						if(combineSample.getEndTime().before(currentEndTime)) {
							combineSample.setEndTime(currentEndTime);
							combineSample.setDuration((currentEndTime.getTime() - combineSample.getStartTime().getTime())/3600000d);
						}

					}else {
						//没有重合部分，开始新的持续覆冰样本
						combineSamples.add(combineSample);
						combineSample = sample;
					}
				}else {
					//新的覆冰设备
					if(combineSample != null) {
						combineSamples.add(combineSample);
					}
					combineSample = sample;
				}
				lastMonitorId = sample.getMonitorId();
			}
			if(combineSample != null) {
				combineSamples.add(combineSample);
			}
			return combineSamples;
		}
		private Map<String,IceMonitor> getMonitorMapBasePhase() {
			List<IceMonitor> monitors = monitorManager.findAll();
			Map<String,IceMonitor> map = new HashMap<>();
			for(IceMonitor m:monitors) {
				map.put(m.getBs_id()+","+m.getPhase(), m);
			}
			return map;
		}
		private Map<String,IceMonitor> getMonitorMapBaseBs() {
			List<IceMonitor> monitors = monitorManager.findAll();
			Map<String,IceMonitor> map = new HashMap<>();
			for(IceMonitor m:monitors) {
				map.put(m.getBs_id(), m);
			}
			return map;
		}
	}
	
	
	
}
